//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package io.lumine.xikage.mythicmobs.skills.projectiles;

import io.lumine.xikage.mythicmobs.MythicMobs;
import io.lumine.xikage.mythicmobs.adapters.AbstractEntity;
import io.lumine.xikage.mythicmobs.adapters.AbstractLocation;
import io.lumine.xikage.mythicmobs.adapters.AbstractVector;
import io.lumine.xikage.mythicmobs.adapters.bukkit.BukkitAdapter;
import io.lumine.xikage.mythicmobs.io.MythicLineConfig;
import io.lumine.xikage.mythicmobs.items.MythicItem;
import io.lumine.xikage.mythicmobs.logging.MythicLogger;
import io.lumine.xikage.mythicmobs.logging.MythicLogger.DebugLevel;
import io.lumine.xikage.mythicmobs.mobs.ActiveMob;
import io.lumine.xikage.mythicmobs.mobs.MythicMob;
import io.lumine.xikage.mythicmobs.skills.*;
import io.lumine.xikage.mythicmobs.skills.placeholders.parsers.PlaceholderFloat;
import io.lumine.xikage.mythicmobs.skills.placeholders.parsers.PlaceholderInt;
import io.lumine.xikage.mythicmobs.skills.variables.VariableRegistry;
import io.lumine.xikage.mythicmobs.util.annotations.MythicField;
import io.lumine.xikage.mythicmobs.utils.Events;
import io.lumine.xikage.mythicmobs.utils.Schedulers;
import io.lumine.xikage.mythicmobs.utils.items.ItemFactory;
import io.lumine.xikage.mythicmobs.utils.promise.Promise;
import io.lumine.xikage.mythicmobs.utils.terminable.Terminable;
import io.lumine.xikage.mythicmobs.utils.terminable.TerminableRegistry;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.*;
import org.bukkit.event.entity.EntityChangeBlockEvent;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.bukkit.inventory.ItemStack;
import top.yunmouren.bukkit.util.BoundingBox;
import org.bukkit.util.EulerAngle;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class Projectile extends SkillMechanic {
    public static final Set<AbstractEntity> BULLET_ENTITIES = ConcurrentHashMap.newKeySet();
    protected Optional<Skill> onTickSkill = Optional.empty();
    protected Optional<Skill> onHitSkill = Optional.empty();
    protected Optional<Skill> onEndSkill = Optional.empty();
    protected Optional<Skill> onStartSkill = Optional.empty();
    protected String onTickSkillName;
    protected String onHitSkillName;
    protected String onEndSkillName;
    protected String onStartSkillName;
    protected Projectile.ProjectileType type;
    protected boolean fromOrigin;
    protected Projectile.BulletType bulletType;
    protected Material bulletMaterial = null;
    protected int bulletModelId = 0;
    protected MythicItem bulletMythicItem = null;
    protected Projectile bulletProjectile = null;
    protected MythicMob bulletMob = null;
    protected float bulletSpin = 0.0F;
    protected boolean bulletMatchDirection = false;
    protected PlaceholderInt duration;
    protected int tickInterval;
    protected float ticksPerSecond;
    protected float hitRadius;
    protected float verticalHitRadius;
    protected float maxDistanceSquared;
    protected float startYOffset;
    protected float startForwardOffset;
    protected float startSideOffset;
    protected float endSideOffset;
    protected float targetYOffset;
    protected PlaceholderFloat projectileVelocity;
    protected float projectileVelocityVertOffset;
    protected float projectileVelocityHorizOffset;
    protected float projectileVelocityAccuracy;
    protected float projectileVelocityVertNoise;
    protected float projectileVelocityHorizNoise;
    protected float projectileVelocityVertNoiseBase;
    protected float projectileVelocityHorizNoiseBase;
    protected boolean stopOnHitEntity;
    protected boolean stopOnHitGround;
    protected boolean powerAffectsVelocity = true;
    protected boolean powerAffectsRange = true;
    protected boolean hitSelf = false;
    protected boolean hitTarget = true;
    protected boolean hitPlayers = true;
    protected boolean hitNonPlayers = false;
    protected boolean hitTargetOnly = false;
    protected String hitConditionString;
    @MythicField(
            name = "hitConditions",
            aliases = {"conditions", "cond", "c"},
            defValue = "NONE",
            version = "4.9",
            premium = true,
            description = "Conditions applied to the hit target"
    )
    protected List<SkillCondition> hitConditions = null;

    public Projectile(String skill, MythicLineConfig mlc) {
        super(skill, mlc);
        this.onTickSkillName = mlc.getString(new String[]{"ontickskill", "ontick", "ot", "skill", "s", "meta", "m"});
        this.onHitSkillName = mlc.getString(new String[]{"onhitskill", "onhit", "oh"});
        this.onEndSkillName = mlc.getString(new String[]{"onendskill", "onend", "oe"});
        this.onStartSkillName = mlc.getString(new String[]{"onstartskill", "onstart", "os"});
        String type = mlc.getString("type", "NORMAL");

        try {
            this.type = Projectile.ProjectileType.valueOf(type.toUpperCase());
        } catch (Exception var10) {
            MythicLogger.errorMechanicConfig(this, mlc, "Invalid projectile type specified");
            this.type = Projectile.ProjectileType.NORMAL;
        }

        String bulletType = mlc.getString(new String[]{"bullettype", "bullet", "b"}, "NONE", new String[0]);

        try {
            this.bulletType = Projectile.BulletType.valueOf(bulletType.toUpperCase());
        } catch (Exception var9) {
            MythicLogger.errorMechanicConfig(this, mlc, "Invalid bullet type specified");
            this.bulletType = Projectile.BulletType.NONE;
        }

        switch (this.bulletType) {
            case BLOCK:
            case ITEM:
            case SMALLBLOCK:
                String strBulletMaterial = mlc.getString(new String[]{"bulletmaterial", "material", "mat"}, "STONE", new String[0]);

                try {
                    this.bulletMaterial = Material.valueOf(strBulletMaterial.toUpperCase());
                    this.bulletModelId = mlc.getInteger(new String[]{"bulletmodel", "model"}, 0);
                } catch (Exception var8) {
                    MythicLogger.errorMechanicConfig(this, mlc, "Specified bullet material does not exist");
                    this.bulletMaterial = Material.STONE;
                }
                break;
            case MYTHICITEM:
                String strMythicItem = mlc.getString(new String[]{"bulletmaterial", "material", "mat"}, "STONE", new String[0]);
                Optional<MythicItem> maybeItem = MythicMobs.inst().getItemManager().getItem(strMythicItem);
                maybeItem.ifPresent((mythicItem) -> {
                    this.bulletMythicItem = mythicItem;
                });
                break;
            case MOB:
                this.bulletMatchDirection = mlc.getBoolean(new String[]{"bulletmatchdirection", "bmd"}, false);
        }

        this.bulletSpin = mlc.getFloat(new String[]{"bulletspin", "bspin"}, 0.0F);
        this.tickInterval = mlc.getInteger(new String[]{"interval", "int", "i"}, 4);
        this.ticksPerSecond = 20.0F / (float) this.tickInterval;
        this.hitRadius = mlc.getFloat(new String[]{"horizontalradius", "hradius", "hr", "r"}, 1.25F);
        this.duration = mlc.getPlaceholderInteger(new String[]{"maxduration", "maxduration", "md", "d"}, 400, new String[0]);
        float range = mlc.getFloat(new String[]{"maxrange", "mr"}, 40.0F);
        this.maxDistanceSquared = range * range;
        this.verticalHitRadius = mlc.getFloat(new String[]{"verticalradius", "vradius", "vr"}, this.hitRadius);
        this.startYOffset = mlc.getFloat(new String[]{"startyoffset", "syo"}, 1.0F);
        this.startForwardOffset = mlc.getFloat(new String[]{"forwardoffset", "startfoffset", "sfo", "fo"}, 1.0F) * -1.0F;
        this.targetYOffset = mlc.getFloat(new String[]{"targetyoffset", "targety", "tyo"}, 0.0F);
        float sideOffset = mlc.getFloat(new String[]{"sideoffset", "soffset", "so"}, 0.0F);
        this.startSideOffset = mlc.getFloat(new String[]{"startsideoffset", "ssoffset", "sso"}, sideOffset);
        this.endSideOffset = mlc.getFloat(new String[]{"endoffset", "esoffset", "eso"}, sideOffset);
        this.projectileVelocity = mlc.getPlaceholderFloat(new String[]{"velocity", "v"}, 5.0F, new String[0]);
        this.projectileVelocityVertOffset = mlc.getFloat(new String[]{"verticaloffset", "vo"}, 0.0F);
        this.projectileVelocityHorizOffset = mlc.getFloat(new String[]{"horizontaloffset", "ho"}, 0.0F);
        this.projectileVelocityAccuracy = mlc.getFloat(new String[]{"accuracy", "ac", "a"}, 1.0F);
        float defNoise = (1.0F - this.projectileVelocityAccuracy) * 45.0F;
        this.projectileVelocityVertNoise = mlc.getFloat(new String[]{"verticalnoise", "vn"}, defNoise) / 10.0F;
        this.projectileVelocityHorizNoise = mlc.getFloat(new String[]{"horizontalnoise", "hn"}, defNoise);
        this.projectileVelocityVertNoiseBase = 0.0F - this.projectileVelocityVertNoise / 2.0F;
        this.projectileVelocityHorizNoiseBase = 0.0F - this.projectileVelocityHorizNoise / 2.0F;
        this.stopOnHitEntity = mlc.getBoolean(new String[]{"stopatentity", "se"}, true);
        this.stopOnHitGround = mlc.getBoolean(new String[]{"stopatblock", "sb"}, true);
        this.powerAffectsVelocity = mlc.getBoolean(new String[]{"poweraffectsvelocity", "pav"}, true);
        this.powerAffectsRange = mlc.getBoolean(new String[]{"poweraffectsrange", "par"}, true);
        this.hitSelf = mlc.getBoolean(new String[]{"hitself"}, false);
        this.hitPlayers = mlc.getBoolean(new String[]{"hitplayers", "hp"}, true);
        this.hitNonPlayers = mlc.getBoolean(new String[]{"hitnonplayers", "hnp"}, false);
        this.hitTarget = mlc.getBoolean(new String[]{"hittarget", "ht"}, true);
        this.hitTargetOnly = mlc.getBoolean(new String[]{"hittargetonly", "hto"}, false);
        this.hitConditionString = mlc.getString(new String[]{"hitconditions", "conditions", "cond", "c"}, "null", new String[0]);
        this.fromOrigin = mlc.getBoolean(new String[]{"fromorigin", "fo"}, false);
        getPlugin().getSkillManager().queueSecondPass(() -> {
            if (this.onTickSkillName != null) {
                this.onTickSkill = getPlugin().getSkillManager().getSkill(this.onTickSkillName);
            }

            if (this.onHitSkillName != null) {
                this.onHitSkill = getPlugin().getSkillManager().getSkill(this.onHitSkillName);
            }

            if (this.onEndSkillName != null) {
                this.onEndSkill = getPlugin().getSkillManager().getSkill(this.onEndSkillName);
            }

            if (this.onStartSkillName != null) {
                this.onStartSkill = getPlugin().getSkillManager().getSkill(this.onStartSkillName);
            }

            if (this.bulletType == Projectile.BulletType.MOB) {
                String mobType = mlc.getString(new String[]{"mob", "mobtype", "mm"}, "SkeletalKnight", new String[0]);
                this.bulletMob = getPlugin().getMobManager().getMythicMob(mobType);
                if (this.bulletMob == null) {
                    MythicLogger.errorMechanicConfig(this, mlc, "Invalid bullet mob type specified");
                    this.bulletType = Projectile.BulletType.NONE;
                }
            }

            if (this.hitConditionString != null && MythicMobs.isVolatile()) {
                this.hitConditions = getPlugin().getSkillManager().getConditions(this.hitConditionString);
            }

        });
    }

    public float getTargetYOffset() {
        return this.targetYOffset;
    }

    protected static enum ProjectileType {
        NORMAL,
        METEOR;

        private ProjectileType() {
        }
    }

    protected static enum BulletType {
        NONE,
        ITEM,
        BLOCK,
        SMALLBLOCK,
        MYTHICITEM,
        ARMOR_STAND,
        ARROW,
        MOB;

        private BulletType() {
        }
    }

    public abstract class ProjectileTracker implements IParentSkill, Runnable, Terminable {
        protected final TerminableRegistry components = TerminableRegistry.create();
        protected SkillMetadata data;
        protected int chargesRemaining;
        protected int ticksRemaining;
        protected AbstractEntity bullet = null;
        protected float power;
        protected AbstractLocation startLocation;
        protected AbstractLocation previousLocation;
        protected AbstractLocation currentLocation;
        protected AbstractVector currentVelocity;
        protected Set<AbstractEntity> inRange = ConcurrentHashMap.newKeySet();
        protected HashSet<AbstractEntity> targets = new HashSet();
        protected HashMap<AbstractEntity, Long> immune = new HashMap();

        public ProjectileTracker(SkillMetadata data, AbstractLocation target) {
            this.data = data.deepClone();
            this.data.setCallingEvent(this);
            this.data.setIsAsync(true);
            this.power = data.getPower();
            this.ticksRemaining = Projectile.this.duration.get(data);
            MythicLogger.debug(DebugLevel.MECHANIC, "++ Projectile fired by Entity {0}: skill = {1}", new Object[]{data.getCaster().getEntity().getName(), Projectile.this.line});
        }

        private void evaluatePotentialTargets() {
            this.inRange.clear();
            if (Projectile.this.hitSelf || Projectile.this.hitPlayers || Projectile.this.hitNonPlayers) {
                this.inRange.addAll(AbstractSkill.getPlugin().getVolatileCodeHandler().getWorldHandler().getEntitiesNearLocation(this.currentLocation, (double) Projectile.this.hitRadius));
                this.inRange.removeIf((e) -> {
                    if (e == null) {
                        return true;
                    } else if (e.isLiving() && !e.isCitizensNPC()) {
                        if (!Projectile.this.hitSelf && e.getUniqueId().equals(this.data.getCaster().getEntity().getUniqueId())) {
                            return true;
                        } else if (!Projectile.this.hitPlayers && e.isPlayer()) {
                            return true;
                        } else if (!Projectile.this.hitNonPlayers && !e.isPlayer()) {
                            return true;
                        } else if (this.bullet != null && this.bullet.getUniqueId().equals(e.getUniqueId())) {
                            return true;
                        } else {
                            if (Projectile.this.hitConditions != null) {
                                Iterator var2 = Projectile.this.hitConditions.iterator();

                                while (var2.hasNext()) {
                                    SkillCondition condition = (SkillCondition) var2.next();
                                    if (!condition.evaluateEntity(e)) {
                                        return true;
                                    }
                                }
                            }

                            return false;
                        }
                    } else {
                        return true;
                    }
                });
            }

        }

        public boolean executeProjectileSkill(Optional<Skill> skill, SkillMetadata data, boolean atCaster) {
            if (skill == null) {
                return true;
            } else if (!skill.isPresent()) {
                return true;
            } else {
                data = data.deepClone();
                if (atCaster) {
                    data.setEntityTarget(data.getCaster().getEntity());
                }

                if (((Skill) skill.get()).isUsable(data)) {
                    VariableRegistry vars = data.getVariables();
                    ((Skill) skill.get()).execute(data);
                    return true;
                } else {
                    return false;
                }
            }
        }

        public void setPower(float p) {
            this.power = p;
        }

        public void modifyPower(float p) {
            this.power *= p;
        }

        public BoundingBox getBoundingBox() {
            return BoundingBox.of(
                    BukkitAdapter.adapt(this.currentLocation),
                    (double) Projectile.this.hitRadius,
                    (double) Projectile.this.verticalHitRadius,
                    (double) Projectile.this.hitRadius
            );
        }

        public boolean start() {
            this.components.accept(Events.subscribe(WorldUnloadEvent.class).filter((event) -> {
                return event.getWorld().getUID().equals(this.startLocation.getWorld().getUniqueId());
            }).handler((event) -> {
                this.terminate();
            }));
            Promise.start().thenRunSync(() -> {
                this.projectileStart();
                if (Projectile.this.bulletType != Projectile.BulletType.NONE) {
                    this.spawnBullet();
                    if (Projectile.this.bulletType == Projectile.BulletType.BLOCK) {
                        this.components.accept(Events.subscribe(EntityChangeBlockEvent.class).filter((event) -> {
                            return event.getEntity().getUniqueId().equals(this.bullet.getUniqueId());
                        }).handler((event) -> {
                            event.setCancelled(true);
                        }));
                    } else if (Projectile.this.bulletType != Projectile.BulletType.MOB && Projectile.this.bulletType != Projectile.BulletType.ARMOR_STAND) {
                        if (Projectile.this.bulletType == Projectile.BulletType.ARROW) {
                            this.components.accept(Events.subscribe(EntityDamageByEntityEvent.class).filter((event) -> {
                                return this.bullet != null;
                            }).filter((event) -> {
                                return event.getDamager() != null;
                            }).filter((event) -> {
                                return event.getDamager().getUniqueId().equals(this.bullet.getUniqueId());
                            }).handler((event) -> {
                                event.setCancelled(true);
                            }));
                        }
                    } else {
                        this.components.accept(Events.subscribe(PlayerInteractEntityEvent.class).filter((event) -> {
                            return event.getRightClicked() != null;
                        }).filter((event) -> {
                            return event.getRightClicked().getUniqueId().equals(this.bullet.getUniqueId());
                        }).handler((event) -> {
                            event.setCancelled(true);
                        }));
                    }
                }

                if (Projectile.this.onStartSkill.isPresent() && ((Skill) Projectile.this.onStartSkill.get()).isUsable(this.data)) {
                    SkillMetadata sData = this.data.deepClone();
                    HashSet<AbstractLocation> targets = new HashSet();
                    targets.add(this.startLocation);
                    sData.setLocationTargets(targets);
                    sData.setOrigin(this.currentLocation.clone());
                    ((Skill) Projectile.this.onStartSkill.get()).execute(sData);
                }

                this.run();
                this.components.accept(Schedulers.sync().runRepeating(this, 0L, (long) Projectile.this.tickInterval));
            });
            return true;
        }

        public void run() {
            this.ticksRemaining -= Projectile.this.tickInterval;
            if (this.data.getCaster() != null && this.data.getCaster().getEntity().isDead()) {
                this.terminate();
            } else if (this.currentLocation.distanceSquared(this.startLocation) >= (double) Projectile.this.maxDistanceSquared) {
                this.terminate();
            } else if (this.ticksRemaining <= 0) {
                this.terminate();
            } else {
                this.evaluatePotentialTargets();
                this.projectileTick();
            }
        }

        public abstract void projectileStart();

        public abstract void projectileTick();

        public void projectileEnd() {
        }

        public void close() {
            if (!this.components.hasTerminated()) {
                this.projectileEnd();
                if (Projectile.this.bulletType != Projectile.BulletType.NONE) {
                    Schedulers.sync().runLater(() -> {
                        if (this.bullet != null) {
                            this.bullet.remove();
                            Projectile.BULLET_ENTITIES.remove(this.bullet);
                        }

                    }, 2L);
                }

                if (Projectile.this.onEndSkill.isPresent() && Projectile.this.onEndSkill.get().isUsable(this.data)) {
                    SkillMetadata sData = this.data.deepClone();
                    Projectile.this.onEndSkill.get().execute(sData.setOrigin(this.currentLocation).setLocationTarget(this.currentLocation));
                }

                if (this.inRange != null) {
                    this.inRange.clear();
                }
            }

            this.components.closeAndReportException();
        }

        public void setCancelled() {
            this.terminate();
        }

        public boolean getCancelled() {
            return this.components.hasTerminated();
        }

        private void spawnBullet() {
            AbstractLocation l;
            Location loc;
            if (Projectile.this.bulletType == Projectile.BulletType.ARMOR_STAND) {
                if (this.hasTerminated()) {
                    return;
                }
                l = this.currentLocation.clone().subtract(0.0D, 0.5D, 0.0D);
                loc = BukkitAdapter.adapt(l);
                ArmorStand armorStand;
                armorStand = (ArmorStand) MythicMobs.inst().getVolatileCodeHandler().getWorldHandler().spawnInvisibleArmorStand(loc);
                armorStand.setAI(true);
                armorStand.setTicksLived(2147483647);
                armorStand.setInvulnerable(true);
                armorStand.setGravity(false);
                this.bullet = BukkitAdapter.adapt(armorStand);
                Projectile.BULLET_ENTITIES.add(this.bullet);
                AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                if (this.hasTerminated()) {
                    armorStand.remove();
                }
            } else if (Projectile.this.bulletType == Projectile.BulletType.BLOCK) {
                if (this.hasTerminated()) {
                    return;
                }

                l = this.currentLocation.clone().subtract(0.0D, 0.5D, 0.0D);
                FallingBlock block = BukkitAdapter.adapt(l).getWorld().spawnFallingBlock(BukkitAdapter.adapt(l), Projectile.this.bulletMaterial, (byte) 0);
                block.setHurtEntities(false);
                block.setDropItem(false);
                block.setTicksLived(2147483647);
                block.setInvulnerable(true);
                block.setGravity(false);
                this.bullet = BukkitAdapter.adapt(block);
                Projectile.BULLET_ENTITIES.add(this.bullet);
                AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                if (this.hasTerminated()) {
                    block.remove();
                }
            } else {
                ItemStack i;
                AbstractLocation lx;
                Item item;
                if (Projectile.this.bulletType == Projectile.BulletType.MYTHICITEM) {
                    i = BukkitAdapter.adapt(Projectile.this.bulletMythicItem.generateItemStack(1));
                    lx = this.currentLocation.clone().subtract(0.0D, 0.35D, 0.0D);
                    item = BukkitAdapter.adapt(lx).getWorld().dropItem(BukkitAdapter.adapt(lx), i);
                    item.setTicksLived(2147483647);
                    item.setInvulnerable(true);
                    item.setGravity(false);
                    item.setPickupDelay(2147483647);
                    this.bullet = BukkitAdapter.adapt(item);
                    Projectile.BULLET_ENTITIES.add(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setItemPosition(this.bullet, lx);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().sendEntityTeleportPacket(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                    if (this.hasTerminated()) {
                        item.remove();
                    }
                } else if (Projectile.this.bulletType == Projectile.BulletType.SMALLBLOCK) {
                    l = this.currentLocation.clone();
                    ArmorStand blockx = (ArmorStand) BukkitAdapter.adapt(l).getWorld().spawnEntity(BukkitAdapter.adapt(l), EntityType.ARMOR_STAND);
                    blockx.setCustomName("Dinnerbone");
                    blockx.setCustomNameVisible(false);
                    blockx.setHeadPose(new EulerAngle(0.0D, 0.0D, 0.0D));
                    blockx.getEquipment().setHelmet(new ItemStack(Projectile.this.bulletMaterial));
                    blockx.setArms(false);
                    blockx.setBasePlate(false);
                    blockx.setVisible(false);
                    blockx.setTicksLived(2147483647);
                    blockx.setInvulnerable(true);
                    this.bullet = BukkitAdapter.adapt(blockx);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setArmorStandNoGravity(this.bullet);
                    Projectile.BULLET_ENTITIES.add(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                    if (this.hasTerminated()) {
                        blockx.remove();
                    }
                } else if (Projectile.this.bulletType == Projectile.BulletType.ITEM) {
                    i = ItemFactory.of(Projectile.this.bulletMaterial).model(Projectile.this.bulletModelId).build();
                    lx = this.currentLocation.clone().subtract(0.0D, 0.35D, 0.0D);
                    item = BukkitAdapter.adapt(lx).getWorld().dropItem(BukkitAdapter.adapt(lx), i);
                    item.setTicksLived(2147483647);
                    item.setInvulnerable(true);
                    item.setGravity(false);
                    item.setPickupDelay(2147483647);
                    this.bullet = BukkitAdapter.adapt(item);
                    Projectile.BULLET_ENTITIES.add(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setItemPosition(this.bullet, lx);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().sendEntityTeleportPacket(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                    if (this.hasTerminated()) {
                        item.remove();
                    }
                } else if (Projectile.this.bulletType == Projectile.BulletType.MOB && Projectile.this.bulletMob != null) {
                    l = this.previousLocation.clone().subtract(0.0D, 1.35D, 0.0D);
                    ActiveMob am = Projectile.this.bulletMob.spawn(l, 1.0D, io.lumine.xikage.mythicmobs.mobs.entities.SpawnReason.OTHER, (entityx) -> {
                        entityx.setTicksLived(2147483647);
                        entityx.setInvulnerable(true);
                        entityx.setVelocity(BukkitAdapter.adapt(this.currentVelocity));
                        if (entityx.getType().equals(EntityType.ARMOR_STAND)) {
                            AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setArmorStandNoGravity(BukkitAdapter.adapt(entityx));
                            ((ArmorStand) entityx).setAI(true);
                            ((ArmorStand) entityx).setRemoveWhenFarAway(true);
                        } else {
                            entityx.setGravity(false);
                            if (entityx instanceof LivingEntity) {
                                ((LivingEntity) entityx).setRemoveWhenFarAway(true);
                            }
                        }

                    });
                    Entity entity = am.getEntity().getBukkitEntity();
                    am.setParent(this.data.getCaster());
                    am.setOwner(this.data.getCaster().getEntity().getUniqueId());
                    this.bullet = BukkitAdapter.adapt(entity);
                    Projectile.BULLET_ENTITIES.add(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                    if (this.hasTerminated()) {
                        this.bullet.remove();
                    }

                    this.bullet.teleport(this.currentLocation.clone().subtract(0.0D, 1.35D, 0.0D));
                } else if (Projectile.this.bulletType == Projectile.BulletType.ARROW) {
                    l = this.currentLocation.clone();
                    loc = BukkitAdapter.adapt(l);
                    Arrow arrow = (Arrow) loc.getWorld().spawn(loc, Arrow.class);
                    arrow.setTicksLived(2147483647);
                    arrow.setInvulnerable(true);
                    arrow.setGravity(false);
                    arrow.setVelocity(BukkitAdapter.adapt(this.currentVelocity).multiply(0.25D));
                    this.bullet = BukkitAdapter.adapt(arrow);
                    Projectile.BULLET_ENTITIES.add(this.bullet);
                    AbstractSkill.getPlugin().getVolatileCodeHandler().getEntityHandler().setHitBox(this.bullet, 0.0D, 0.0D, 0.0D);
                    if (this.hasTerminated()) {
                        arrow.remove();
                    }
                }
            }

            if (this.bullet != null) {
                this.applyBulletVelocity();
            }

        }

        public abstract void applyBulletVelocity();

        public int getChargesRemaining() {
            return this.chargesRemaining;
        }

        public int getTicksRemaining() {
            return this.ticksRemaining;
        }
    }
}
